feat(workspaces): group clones by git remote and recover stuck gateway sessions - #223
Closed
crimsonsunset wants to merge 187 commits into
Closed
feat(workspaces): group clones by git remote and recover stuck gateway sessions#223crimsonsunset wants to merge 187 commits into
crimsonsunset wants to merge 187 commits into
Conversation
Autonomous decisions: - Reverted packages/ui/src/components/layout/Sidebar.tsx and AppShell.tsx to main versions — main is ahead of i18n on these files (accent strip, hint prop, group-hover animations); porting i18n's older versions would have been a regression. - Ported apps/desktop/src/lib/api/ shim files (app.ts, configExport.ts, settings.ts, transport.ts, oauth.ts, serverClone.ts, workspaceAppearances.ts, fetch-api.ts/helpers/types) — required by backend/shell/index.ts and build-info.helpers.ts; all are @deprecated re-export shims pointing at the new backend facade. - In api/index.ts: selective named exports from oauth.ts instead of export * — avoids duplicate symbol conflicts with existing gateway.ts which still exports OAuthClient, RegistrationType, UpdateClientRequest, and the OAuth client CRUD functions; only oauth.ts-unique additions (flushPendingDeepLink, ConsentRequestDetails, getPendingConsent, approveOAuthConsent) are re-exported. - Ported scripts/build-date.helpers.mjs alongside the spec'd scripts — it is a peer dependency of build-stamp.mjs and build-web-admin.mjs; omitting it would make those scripts fail at runtime. - Updated apps/desktop/src/lib/api/index.ts to export new api shim modules — required so backend/index.ts export * from '../api' resolves all symbols the facade depends on.
…epositories Ports the fork's storage schema onto main, renumbered 020-031 to sit after upstream's 016-019. Extends InstalledServer (cloned_from, display_name_override, default_params(+strategy), update_policy, pinned_version, latest_available_version, version_checked_at, current_version), WorkspaceBinding (client_id, label), and FeatureSetMember (surfaced) additively, with the SQLite repos round-tripping the new columns. Adds embedding_repository + workspace_appearance_repository and their core traits/entity. Autonomous decisions: - Kept main's exact-match WorkspaceBinding resolution (find_exact_for_roots); did NOT port i18n's longest-prefix + client-scope resolver — per orchestrator Choice A. client_id/label are persisted additively but stay global (None) today; per-client routing is a later gateway phase. - Did not wire the new repos into ApplicationServices — i18n itself doesn't wire them there, and their consumers (gateway embedding/discovery services, workspace-appearance commands) land in later phases. Repos are crate-exported and unit-tested. - Kept main's stronger InstalledServerRepository semantics (build_server -> Result, careful decrypt error distinction) rather than porting i18n's signatures; only the 9 new columns were added to the existing install/update CRUD. Did not add i18n's set_display_name_override/update_version_cache trait methods (later-phase consumers). - Renamed migration 027's internal "-- Migration 023:" comment to 027 to match the renumbered filename. - New feature-set members default surfaced=false at every construction site, matching migration 023's DEFAULT 0 and the entity constructors. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Port the web admin HTTP server stack from the i18n branch to main, reconciled with Phase 1–2 divergences. Autonomous decisions: - Stubbed Phase 5/6/7 features (version probing, clone_server, display name override, public URL persistence, workspace icon upload via the `image` crate) with descriptive errors and ponytail comments — avoids pulling in unported dependencies while preserving the API surface. - Replaced `WorkspaceBinding::new_scoped_multi` (Phase 6) with `new_multi` + manual client_id assignment using the existing API. - Replaced `find_longest_prefix_match` (not yet added to the repo trait) with an inline prefix-scan over `list_for_space`. - Used `option_env!` for MCPMUX_BUILD_* env vars so `cargo check` works outside CI without those variables set. - Added admin settings keys and methods to `AppSettingsService` in mcpmux-core (get/set admin_enabled, admin_port, trust_cf_access, cf_team_domain) — minimal extension, no breaking changes. - Added `space_repository()` accessor to `SpaceService` to avoid exposing the private `repository` field. - Added `test-utils` feature flag to mcpmux-gateway Cargo.toml, used by the ported `#[cfg(feature = "test-utils")]` test helpers. - Wired admin server startup into lib.rs setup closure; registered `reload_admin_server` Tauri command; integrated `emit_ui_channel` into the gateway domain-event bridge for SSE fan-in. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
- Add macos_dock.rs: set_dock_visible() wraps ActivationPolicy + dock visibility, cfg-gated #[cfg(target_os = "macos")] with a no-op stub for other platforms - Add macos_permissions.rs: ensure_contacts_registered() triggers CNContactStore TCC prompt on first launch so McpMux appears in System Settings → Privacy & Security → Contacts; no-op on non-macOS - Add main_window.rs: show_main_window() / hide_main_window_to_tray() helpers used by tray, deep-link focus, and close-to-tray handler - Add Info.plist: NSContactsUsageDescription, NSCalendarsUsageDescription, NSRemindersUsageDescription, NSAppleEventsUsageDescription TCC keys - Wire lib.rs: declare new modules, call ensure_contacts_registered() + set_dock_visible(false) in setup, use main_window helpers throughout - Add commands/workspace_appearance.rs: list/upsert/delete workspace appearances + upload/resolve icon file commands - Register workspace_appearance in commands/mod.rs and invoke_handler - Add DomainEvent::WorkspaceAppearanceChanged to mcpmux-core and handle in gateway ui_events + desktop gateway bridge - Add target-specific macOS Cargo deps: objc2, objc2-foundation, objc2-contacts, block2 Autonomous decisions: - WorkspaceBinding.icon check in maybe_remove_orphaned_icon_file deferred to Phase 7 (field not yet on the entity); left a ponytail: comment - DomainEvent::WorkspaceAppearanceChanged added now (minimal addition alongside Phase 2 entity) to unblock the commands compiling Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Port dev's split meta_tools module layout and supporting gateway services from the i18n branch (supersedes upstream consolidated files per plan Decision #6): - Split meta_tools modules: invoke_*, search_tools(_index), list_servers, meta_tool_common, disclosure_*, feature_set_tools, bind_workspace, set_workspace_root, token_budget, approval_broker/types, diagnose_*. - Gateway services: tool_discovery*, embedding, embedding_warmer, discovery_rank, prompt_discovery, resource_discovery. - Wire embedding warmer into MCPNotifier on connect / feature-refresh. - Add WorkspaceBinding::new_scoped_multi and a client-scoped-with-global -fallback find_longest_prefix_match default impl; use it in the admin effective-features bridge. - Additive FeatureService grant helpers, ToolCallResult.structured_content, routing format helpers, session_roots search cache. - Reconcile meta_tool_approval Tauri command with the always-approve broker. package_version / server_version_probe deferred to Phase 6. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
- New Rust services: package_version, server_version_probe, server_clone - New domain events: ServerVersionChecked, ServerUpdateAvailable - InstalledServerRepository: set_display_name_override, update_version_cache - ServerAppService: clone_server, is_clone_id_available, suggest_clone_suffix, list_clone_dependents, set_display_name_override, update_config extended with update_policy + pinned_version - pool/transport/resolution: TransportResolutionOptions with update-policy - New Tauri commands: get_build_info, set_server_display_name, clone_server, is_clone_id_available, suggest_clone_suffix, list_clone_dependents, update_server_package, get/update_server_update_settings, check_all_server_updates, check_server_version - Frontend: CloneAccountModal, UninstallSourceWithClonesDialog, ServerActionMenu (clone + update actions), ServerUpdatesSection, ServerPendingUpdatesList, BuildStampPanel, StaleBuildBanner, use-build-stamp.hook, server-update-policy.helpers, server-pending-updates.helpers, server-display-name.helpers - ServersPage: clone-aware uninstall, handleLockToCurrentVersion, handleUpdateNow, handleCheckForUpdate wired end-to-end - build.rs: embed git SHA, branch, commit/build timestamps Signed-off-by: crimsonsunset <jsangio1@gmail.com>
- Add features/dashboard/ (DashboardPage, DashboardQuickLinks, DashboardRecentActivity, DashboardServerHealth, DashboardStatCards, dashboard.helpers, useDashboardData, index) - Add /dashboard nav entry in navigation.ts + App.tsx route - Port SourceBadge (add clonedFrom prop), source-badge.helpers.ts, AddServerMenu, ServerEnabledToggle, ServersCountSummary, ServersFiltersPopover, servers-page.helpers — hardcoded English - Wire workspace appearances into WorkspacesPage (load/persist icons for unmapped roots, card + inspector live preview, upload via pickPath) - Extend ServerIcon to resolve local:workspace-icons refs - Add SpacePanel slide-out editor in features/spaces/ - Add AboutSection to features/settings/ - Add useMetaToolEvents, useOAuthClientEvents, useWorkspaceEvents shims - Reconcile useServerManager to use useDomainEvents subscribe facade - Add pendingServersFilter state/action/selector to appStore - Add update_space Tauri command + SpaceService.update + updateSpace API - Wire resolveInstalledDisplayName into registryStore.mergeServers SpaceSwitcher already uses spaceAccentTint + space.icon (no change needed). pnpm validate clean; no react-i18next in this phase. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Port react-i18next infrastructure and stringified UI from i18n branch onto the reconciled Phases 1–7 port branch. Reconcile nav IA, meta-tools, workspaces, registry analytics, and Phase 5–7 locale keys while keeping port functionality intact. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Dashboard is the sole default nav with accent stat tiles, onboarding strip, and live stat refresh; Home page and nav entry are removed. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Detect fork v16–27 ledger, apply upstream v16–19 SQL, and stamp v31 without re-running fork migrations that would duplicate columns. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
My Servers, Search, Bundles, Projects, and Clients labels with matching icons; Bundles keeps a FeatureSets tooltip; e2e selectors updated. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Autonomous decisions: - Meta-tool fixes (7e1ab44, de5ccb4, e428dcf) — no patch; dev-rebased already matches dev functionally - Server-update probe fixes (d0d0232, 92e340f, c489692) — no patch; package_version, server_version_probe, and resolution.rs already aligned - ServersPage / server-update-policy.helpers — no patch; shouldShowPackageUpdate already present; kept i18n getUpdatePolicyOptions over dev's hardcoded UPDATE_POLICY_OPTIONS - write_runtime.rs — manual patch from 7414f75; wire version_probe, apply_package_update on explicit update, post-update probe to clear stale badges Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Autonomous decisions: - Delegate openSpaceConfigFile, openUrl, addToVscode/addToCursor to shell — desktop-only commands need web-safe fallbacks without new HTTP routes - Fix update_space route to accept flat Tauri args or nested input — Tauri IPC uses flat fields while admin REST expects a JSON body Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Wire missing admin API bridges and fetch-api routes so the web SPA can load spaces, registry, and settings without Tauri invoke. Re-export transport-aware domain events, enable SSE after data sync, and guard remaining listen() call sites for admin-http mode. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Autonomous decisions: - Run prep after backend auto-start — keeps fail-fast health check without breaking cold-start flow - Honor MCPMUX_ADMIN_PORT in Vite proxy — matches dev script port env convention - prep subcommand is health-check only — no port-guard utility exists in repo; Phase 1 scope is liveness Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Wire five config-export admin endpoints matching fetch-api.routes.ts: preview, paths, check, backup, and export. Bridge logic mirrors the existing Tauri config_export commands via ConfigExporter and enabled server resolution through ApplicationServices. Autonomous decisions: - POST handlers live in handlers/write.rs; bridge logic consolidated in command_bridge/read.rs per plan (check/backup/export delegate there). - Enabled servers resolved via list_for_space + filter rather than adding installed_server_repo to AdminBridgeCtx. - Export always writes unmasked credentials, matching desktop Tauri behavior. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Route oauth-consent-request and oauth-client-changed through emit_ui_channel so web admin SSE receives them, and align BuiltinServerConfigChanged SSE mapping to builtin-server-config-changed. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Guard native openDialog/pickPath calls with isTauri() and show text path inputs on web admin so base dirs, server config paths, and workspace icons/roots work without crashing the browser. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Remove superseded export_config, connect_server, and disconnect_server_v2 apiCall/Tauri paths; extend admin-transport tests for builtins, config-export routes, direct SSE channels, and dead-command guard. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Document the five-phase web admin completion work and apply rustfmt drift from Phases 2–3 command bridge and ui_events changes. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
…ion plan Partial Phase 1 work: list_tools uses get_advertised_tools_for_grants again. Includes database.rs formatting cleanup and the planning doc. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Restore get_advertised_prompts_for_grants and get_advertised_resources_for_grants in facade.rs; wire list_prompts, get_prompt, list_resources, and read_resource to filter through surfaced feature IDs (list_tools already fixed in 93e6bef). Autonomous decisions: - Used get_fetchable_prompts / get_readable_resources as invokable base — matches dev branch and existing facade aliases Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Restore dev hard-cut model on call_tool, get_prompt, and read_resource: non-surfaced invokable features redirect to meta-tool paths; inactive tools get bind_feature_set hints via list_inactive_discovery_tools. Re-export format_direct_* helpers from pool/mod.rs. Restore structured_content passthrough on call_tool results. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Restore file-key credential migration at startup, WorkspaceNeedsBinding collision_client_id alongside space_locked, and OAuth refresh dedup singleton. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Autonomous decisions: - Marked surfacing Phases 1–3 complete with commit SHAs (126fa2f, b131a3f, 6c4d6b7) - Audited post-port Phases 1–2 as complete (784cd41, 9747c71); Phase 3 manual QA documented - No code fixes needed — all automated gates passed on HEAD manual QA required: full post-port Phase 3 feature walkthrough (dashboard, i18n, spaces, servers, feature sets, workspaces, clients, registry, builtins, settings, meta-tools via MCP client, web admin SSE/CF Access, surfacing smoke test) Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Land label/icon metadata on workspace bindings, appearance commands, and Projects UI ahead of machine-binding work on feat branch. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Test called the undefined render() instead of the i18n-aware helper used elsewhere in the file, failing every run. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Autonomous decisions: - Cap fork reconcile ledger at v32 — newer migrations must run via the normal loop, not be stamped applied during reconcile - Preserve existing.machine_id in Tauri/admin binding update literals — compile-only ripple until Phase 3 wires machine_id through inputs Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Move the throwaway .scratch wrapper into scripts/cursor-env-probe.mjs so mcp-remote still execs after logging argv/env/pwd, plus a summarizer that reprints the Aug 20 substitution cuts. pnpm probe:cursor-env prints the mcp.json swap; probe:cursor-env:summary reads $HOME/Desktop/mcpmux-env-probe.log. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
FeatureService.mark_unavailable wrote the repo flip but left resolve_feature_sets serving the pre-disconnect tool list. Invalidate the space cache there (and on delete_for_server) so clients stop seeing tools from a dead server. TS suite was failing on stale workspaceBindings mocks missing isIdBinding, persist writes against a missing localStorage, and a wizard payload that now includes label. importOriginal those mocks, install a memory store in setup, and match the create args. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Cursor leaves ${workspaceFolder} unsubstituted on many mcp-remote spawns,
so a session arrives with an empty header and no way to resolve a route.
A manual mcpmux_set_workspace_root fixed only that one session, so every
Reload MCP cost another call.
Pins now promote to a window keyed on the owning process, letting later
sessions from the same mcp-remote inherit the claim. Inheritance is
re-validated against that session's own open folder set, and pins for
dead processes are evicted on read.
Pin logs name their source so a trace can tell a working header from a
manual recovery, and window-pin writes log once per claim instead of once
per request. The bridge snippet and probe recipe inline the access key,
dropping a second substitution that could silently expand to empty.
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Cursor's global bridge shares one mcp-remote process, and therefore one mcp-session-id, across every open window. A set_workspace_root call on that shared session names no window, so promoting it to window_pins handed every other window sharing the process whichever folder happened to be claimed first — confirmed live: a claim made in one window's chat landed on another window's session id and widened its FeatureSet. A header or single-candidate pin is self-attesting proof of single-window intent and still promotes unconditionally. A meta-tool pin now promotes only when the session's own candidate set independently narrows to one folder; otherwise it stays session-scoped and a warn names the skip. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Cursor's hooks channel spawns fresh per agent-lifecycle event with an accurate per-window workspace_roots payload, sidestepping the shared mcp-remote process problem entirely instead of extracting more signal from it. Proposes a best-effort hint tier below window_pins to close the residual manual set_workspace_root cost on the global bridge. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
…e root Add a non-mutating resolve_for_workspace_root path so Cursor preToolUse context can pick a binding without writing session or window pins. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Strip and validate the reserved hook argument, route that call from the exact root, and thread it through MetaToolCall so bind and search stay on the same binding. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Write the Node script under ~/.cursor/hooks, merge one matcher into plain hooks.json with backup, and expose install/status/uninstall on the global Cursor bridge result screen. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Every Cursor window shares one access key, so a parked X-Mcpmux-Workspace could pin a brand-new session onto a folder that window never had open. Refuse the pending header unless it is in the claiming request's candidate set. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Move the installer into the gateway so web admin can write ~/.cursor on the host via /api/v1/cursor-hook, and surface the same controls on every Cursor connection side panel. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
fix(gateway): close aug14 gateway ops bugs
Keep the generation-guarded resolution cache and public invalidate_space so PR 10 and the workspace-routing work coexist. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Empty headers no longer keep a sibling window's pin, and window pins now require a one-folder set. Also lock reconnect_fresh, quote the hook path, and stop the env-probe from dumping secrets. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Empty ${workspaceFolder} no longer wipes a same-request single-folder set.
Failed config reconnect marks features unavailable, bind active uses the
hook root, and the env-probe allowlists study keys only.
Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Lock the portable routing contract (CallRoot / SessionClaim / SurfaceConstraint) so the next harness is Claude Code, not a generalized Cursor hook. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
feat(gateway): exact per-call Cursor workspace routing
Same binding listed different sequences per machine because display used insertion order. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Bindings created from different clone paths now share a card by default, with a panel override to force-link or isolate when origin is missing or wrong. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Cloudflare pruning a cached client_id no longer bricks outbound OAuth. Hook/candidate-set mismatches no longer lock out mcpmux_* recovery tools. Startup also backfills git remotes so existing path bindings group across machines. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
Contributor
Author
|
Opened against the org by default ( |
Merged
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Same repo cloned at different paths now shares one Projects card by default, keyed on the normalized git
originremote. A panel override force-links or isolates when origin is missing or wrong. This PR also unsticks two gateway dead-ends that showed up while using that work: Cloudflare pruning a cached DCRclient_id(invalid_client), and a hook/candidate-set mismatch that locked evenmcpmux_*recovery tools.Changes overview
In ticket / planned scope
originat the Projects card layerproject_link_idoverride to force-link or isolate a bindinggit_remote_urlat bind-create (Tauri IPC and admin HTTP share one helper)Added while implementing
docs/planning/cross-machine-project-linking.md)043_workspace_binding_project_link.sql)Review / hardening
invalid_client(token check, authorize-URL probe, callback)mcpmux_*meta tools stay reachable when the Cursor hook's guessed root is not in the session candidate set; backend tools still hard-failWhat ships
git_remote_url+ optionalproject_link_id; startup backfill for local path rowsclient_idtriggers fresh DCR instead of a dead authorize URLKey technical decisions
apply_detected_git_remote()helper, two create callers. Nogit2crate; shell-out viaconfigure_child_process_platform().project_link_idis the only manual override. No second auto-heuristic (folder name, hash).invalid_clientis the extra signal that the AS forgot the client.Test plan
git_remote_urlwithout clicking Refreshinvalid_clientmcpmux_list_servers/mcpmux_set_workspace_rootwith a hook root outside the session candidate set and confirm they succeed-32602Docs
Related
main(exact-call hook, session pins)